using System;
using System.Collections.Generic;
using System.Linq;

namespace MvcApplication1.Models
{
    public interface IGenericGridArg
    {
        IEnumerable<IGridDataItem> DataItems { get; }
        IEnumerable<IGridColumn> Columns { get; }
    }

    public interface IGridColumn
    {
        RFunc Th { get; }
    }

    public interface IGridDataItem
    {
        IEnumerable<IGridDataItemColumn> Columns { get; }
    }

    public interface IGridDataItemColumn
    {
        RFunc Td { get; }
    }

    public static class GenericGridArg
    {
        public static GenericGridArg<T> New<T>(IEnumerable<T> dataItems, Func<Type<T>, IEnumerable<GridColumn<T>>> columnFunc)
        {
            return new GenericGridArg<T>(dataItems, columnFunc);
        }

        public static GridColumn<T> Column<T>(this Type<T> it, RFunc th, RFunc<T> td)
        {
            return new GridColumn<T>(th, td);
        }
    }

    public class GridColumn<T>: IGridColumn
    {
        public readonly RFunc<T> Td;
        private readonly RFunc th;

        public GridColumn(RFunc th, RFunc<T> td)
        {
            Td = td;
            this.th = th;
        }

        public RFunc Th
        {
            get { return th; }
        }
    }

    public class GenericGridArg<T>: IGenericGridArg
    {
        private readonly IEnumerable<T> dataItems;
        private readonly Func<Type<T>, IEnumerable<GridColumn<T>>> columnFunc;

        public GenericGridArg(IEnumerable<T> dataItems, Func<Type<T>, IEnumerable<GridColumn<T>>> columnFunc)
        {
            this.dataItems = dataItems;
            this.columnFunc = columnFunc;
        }

        public IEnumerable<IGridDataItem> DataItems
        {
            get { return dataItems.Select(_ => new GridDataItem(_, this)); }
        }

        private class GridDataItem : IGridDataItem
        {
            private readonly T dataItem;
            private readonly GenericGridArg<T> parent;

            public GridDataItem(T dataItem, GenericGridArg<T> parent)
            {
                this.dataItem = dataItem;
                this.parent = parent;
            }

            public IEnumerable<IGridDataItemColumn> Columns
            {
                get { return parent.columnFunc(new Type<T>()).Select(_ => new GridDataItemColumn(_, this)); }
            }

            private class GridDataItemColumn : IGridDataItemColumn
            {
                private readonly GridColumn<T> column;
                private readonly GridDataItem parent;

                public GridDataItemColumn(GridColumn<T> column, GridDataItem parent)
                {
                    this.column = column;
                    this.parent = parent;
                }

                public RFunc Td
                {
                    get { return item => column.Td(parent.dataItem); }
                }
            }
        }


        public IEnumerable<IGridColumn> Columns
        {
            get { return columnFunc(new Type<T>()); }
        }
    }
}